// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ecdsa

import (
	"crypto/cipher"
	"crypto/elliptic"
	"internal/cpu"
	"math/big"
)

// kdsa使用给定的函数代码和4096字节的
// 指令。返回值对应于
// 参数块调用“计算数字签名身份验证”
// 指令设置的条件代码。中断的调用由
// 函数处理。
// go:noescape 
func kdsa(fc uint64, params *[4096]byte) (errn uint64)

// testingDisableKDSA强制使用通用回退路径。只能在测试中设置。
var testingDisableKDSA bool

// canUseKDSA检查KDSA指令是否可用，如果可用，则检查
// 曲线的名称，以查看其是否与支持的曲线匹配（P-256、P-384、P-521）。
// 然后，基于曲线名称，将分配函数代码和块大小。
// 如果KDSA指令不可用或曲线不受支持，则canUseKDSA 
// 将“确定”设置为false。
func canUseKDSA(c elliptic.Curve) (functionCode uint64, blockSize int, ok bool) {
	if testingDisableKDSA {
		return 0, 0, false
	}
	if !cpu.S390X.HasECDSA {
		return 0, 0, false
	}
	switch c.Params().Name {
	case "P-256":
		return 1, 32, true
	case "P-384":
		return 2, 48, true
	case "P-521":
		return 3, 80, true
	}
	return 0, 0, false // 不匹配
}

func hashToBytes(dst, hash []byte, c elliptic.Curve) {
	l := len(dst)
	if n := c.Params().N.BitLen(); n == l*8 {
		// 长度为整数字节的曲线的自由分配路径
		if len(hash) >= l {
			// 截断哈希
			copy(dst, hash[:l])
			return
		}
		// 带前导零的pad hash 
		p := l - len(hash)
		for i := 0; i < p; i++ {
			dst[i] = 0
		}
		copy(dst[p:], hash)
		return
	}
	// TODO（mundaym）：避免在此处调用hashToInt 
	hashToInt(hash, c).FillBytes(dst)
}

func sign(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) {
	if functionCode, blockSize, ok := canUseKDSA(c); ok {
		for {
			var k *big.Int
			k, err = randFieldElement(c, *csprng)
			if err != nil {
				return nil, nil, err
			}

			// 参数块的符号如下所示。
			// /+--------------+
			// |签名（R）| 
			// /+--------------+
			// /|签名| 
			// /+--------------+
			// /|散列消息| 
			// /+--------------+
			// /|随机数| 
			// /+--------------
			// /| | 
			// /|
			// /| | 
			// /+--------------+
			// 公共组件（签名器、签名、散列消息、私钥和
			// 随机数）均采用字节块大小。
			// 不同曲线的块大小不同，由canUseKDSA函数设置。
			var params [4096]byte

			// 将内容复制到参数块中。在符号情况下，我们将散列消息、私钥和随机数复制到参数块中。
			hashToBytes(params[2*blockSize:3*blockSize], hash, c)
			priv.D.FillBytes(params[3*blockSize : 4*blockSize])
			k.FillBytes(params[4*blockSize : 5*blockSize])
			// 通过添加8将验证函数代码转换为符号函数代码。
			// 我们还需要通过
			// 添加128来设置函数代码中的“确定性”位，以便在我们提供的随机数之外，使用其自身的随机数
			// 生成器停止指令。
			switch kdsa(functionCode+136, &params) {
			case 0: // 成功
				r = new(big.Int)
				r.SetBytes(params[:blockSize])
				s = new(big.Int)
				s.SetBytes(params[blockSize : 2*blockSize])
				return
			case 1: // 错误
				return nil, nil, errZeroParam
			case 2: // 重试
				continue
			}
			panic("unreachable")
		}
	}
	return signGeneric(priv, csprng, c, hash)
}

func verify(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool {
	if functionCode, blockSize, ok := canUseKDSA(c); ok {
		// 参数块如下所示进行验证：
		// /+--------------+
		// /|签名（R）| 
		// /+--------------+
		// /|签名| 
		// /|散列消息| 
		// /+/+--------------
		// /|公钥X | 
		// /+--------------
		// /|公钥Y | 
		// /+--------------
		// /| | 
		// /+--------------+
		// 公共组件（签名器、签名、哈希消息、公钥X、
		// 和公钥Y）的块大小均为字节。
		// 不同曲线的块大小不同，由canUseKDSA函数设置。
		var params [4096]byte

		// 将内容复制到参数块中。在验证案例中，我们将签名（r）、签名（s）、散列消息、公钥x组件、
		// 和公钥y组件复制到参数块中。
		r.FillBytes(params[0*blockSize : 1*blockSize])
		s.FillBytes(params[1*blockSize : 2*blockSize])
		hashToBytes(params[2*blockSize:3*blockSize], hash, c)
		pub.X.FillBytes(params[3*blockSize : 4*blockSize])
		pub.Y.FillBytes(params[4*blockSize : 5*blockSize])
		return kdsa(functionCode, &params) == 0
	}
	return verifyGeneric(pub, c, hash, r, s)
}
